/*
 * ============================================================================
 *
 *       Filename:  main.c
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  02/05/2018 07:36:40 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *   Organization:  
 *
 * ============================================================================
 */
#include "config.h"
#include <stdio.h>
#include <stdint.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <assert.h>
#include <sys/file.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>

#include "router_msg.h"
#include "netutils.h"
#include "socks5.h"
#include "common.h"
#include "list.h"
#include "log.h"
#include "arg.h"
#include "main.h"
#ifdef CURL_FOUND
#include "http.h"
#endif

#ifdef CURL_FOUND
	int retrytimes = 0;
#endif
#define MAXRETRY	(10)
LIST_HEAD(connections);

void free_conn(conn_t *conn)
{
	sys_debug("close and free conn\n");
	if (conn->buf != NULL) {
		bfree(conn->buf);
		ss_free(conn->buf);
	}
	ss_free(conn->recv_ctx);
	ss_free(conn->send_ctx);
	ss_free(conn);
}

void close_and_free_conn(EV_P_ conn_t *conn)
{
	if (conn != NULL) {
		sys_info("server connection lost, free conn fd: %d\n", conn->fd);
		ev_io_stop(EV_A_ & conn->send_ctx->io);
		ev_io_stop(EV_A_ & conn->recv_ctx->io);
		ev_timer_stop(EV_A_ & conn->send_ctx->watcher);
		if(args_info.heartbeat_arg > 0)
			ev_timer_stop(EV_A_ & conn->heartbeat);
		close(conn->fd);
		free_conn(conn);
		/* reconnect server */
		ev_break (EV_A_ EVBREAK_ALL);
	}
}

static void conn_send_cb(EV_P_ ev_io *w, int revents)
{
	conn_ctx_t *conn_send_ctx = container_of(w, conn_ctx_t, io);
	conn_t *conn = conn_send_ctx->conn;
	conn_ctx_t *conn_recv_ctx = conn->recv_ctx;

	if (conn->buf->len == 0) {
		// close and free
		close_and_free_conn(EV_A_ conn);
		return;
	} else {
		// has data to send
		ssize_t s = send(conn->fd, conn->buf->array + conn->buf->idx,
			conn->buf->len, 0);
		if (s == -1) {
			if (errno != EAGAIN && errno != EWOULDBLOCK) {
				sys_err("conn_send_cb_send");
				close_and_free_conn(EV_A_ conn);
			}
			return;
		} else if (s < (ssize_t)(conn->buf->len)) {
			// partly sent, move memory, wait for the next time to send
			conn->buf->len -= s;
			conn->buf->idx += s;
			return;
		} else {
			// all sent out, wait for reading
			breset(conn->buf);
			ev_io_stop(EV_A_ &conn_send_ctx->io);
			ev_timer_stop(EV_A_ &conn_send_ctx->watcher);
		}
	}

	ev_io_start(EV_A_ &conn_recv_ctx->io);
	return;
}

void free_remote(remote_t *remote)
{
	if (remote->server != NULL) {
		remote->server->remote = NULL;
		remote->server = NULL;
	}
	if (remote->buf != NULL) {
		bfree(remote->buf);
		ss_free(remote->buf);
	}
	ss_free(remote->recv_ctx);
	ss_free(remote->send_ctx);
	ss_free(remote);
}

void close_and_free_remote(EV_P_ remote_t *remote)
{
	if (remote != NULL) {
		sys_debug("close free remote :%d\n", remote->fd);
		ev_timer_stop(EV_A_ & remote->send_ctx->watcher);
		ev_timer_stop(EV_A_ & remote->recv_ctx->watcher);
		ev_io_stop(EV_A_ & remote->send_ctx->io);
		ev_io_stop(EV_A_ & remote->recv_ctx->io);
		close(remote->fd);
		free_remote(remote);
	}
}

void free_server(server_t *server)
{
	sys_debug("close and free server\n");
	if (server->remote != NULL) {
		server->remote->server = NULL;
	}

	if (server->buf != NULL) {
		bfree(server->buf);
		ss_free(server->buf);
	}
	ss_free(server->recv_ctx);
	ss_free(server->send_ctx);
	ss_free(server);
}

void close_and_free_server(EV_P_ server_t *server)
{
	if (server != NULL) {
		sys_info_addr("close and free server", server);
		ev_io_stop(EV_A_ & server->send_ctx->io);
		ev_io_stop(EV_A_ & server->recv_ctx->io);
		ev_timer_stop(EV_A_ & server->send_ctx->watcher);
		ev_timer_stop(EV_A_ & server->recv_ctx->watcher);
		close(server->fd);
		list_del(&server->entries);
		free_server(server);
	}
}


static void remote_recv_cb(EV_P_ ev_io *w, int revents)
{
	remote_ctx_t *remote_recv_ctx = container_of(w, remote_ctx_t, io);
	remote_t *remote              = remote_recv_ctx->remote;
	server_t *server              = remote->server;
	buffer_t *buf;
	buf = server->buf;

	ev_timer_again(EV_A_ &remote->recv_ctx->watcher);
	bconcat_pre(buf, MSS);
	ssize_t r = recv(remote->fd, buf_offset(buf), MSS, 0);
	if (r == 0) {
		// connection closed
		sys_info_addr("remote user destination connection closed", server);
		close_and_free_remote(EV_A_ remote);
		close_and_free_server(EV_A_ server);
		return;
	} else if (r == -1) {
		if (errno == EAGAIN || errno == EWOULDBLOCK) {
			// no data
			// continue to wait for recv
			return;
		} else {
			sys_info_addr("remote user destination connection closed", server);
			close_and_free_remote(EV_A_ remote);
			close_and_free_server(EV_A_ server);
			return;
		}
	} else {
		bconcat_suf(buf, r);
		pr_pkt("remote recv: ", buf->array, buf->len);
	}

	ev_io_stop(EV_A_ & remote_recv_ctx->io);
	ev_io_start(EV_A_ & server->send_ctx->io);
	ev_timer_stop(EV_A_ & server->send_ctx->watcher);
}

static void remote_send_cb(EV_P_ ev_io *w, int revents)
{
	remote_ctx_t *remote_send_ctx = container_of(w, remote_ctx_t, io);
	remote_t *remote              = remote_send_ctx->remote;
	server_t *server              = remote->server;

	if (!remote_send_ctx->connected) {
		struct socks5_response reply = {
			SOCKS5_VERSION,
			SOCKS5_RESPONSE_SUCCESS,
			SOCKS5_RSV,
			SOCKS5_ADDRTYPE_IPV4
		};

		struct sockaddr_storage addr;
		socklen_t len = sizeof(addr);
		memset(&addr, 0, len);
		int r = getpeername(remote->fd, (struct sockaddr *)&addr, &len);
		if (r == 0) {
			sys_info("remote user destination connection success\n");
			remote_send_ctx->connected = 1;
			server->stage = SOCKS5_CONN_STAGE_STREAM;
			ev_io_start(EV_A_ & server->recv_ctx->io);
			getsockname(remote->fd, (struct sockaddr *)&addr, &len);
			struct sockaddr_in *tmp = (void *)&addr;
			/* assert FORCE ipv4 */
			breset(server->buf);
			bconcat(server->buf, (void *)&reply, sizeof(reply));
			bconcat(server->buf, (void *)&tmp->sin_addr, 4);
			bconcat(server->buf, (void *)&tmp->sin_port, 2);
			ev_io_start(EV_A_ &server->send_ctx->io);
			ev_timer_start(EV_A_ &server->send_ctx->watcher);

			ev_io_stop(EV_A_ &remote->send_ctx->io);
			return;
		} else {
			sys_info("remote user destination connection failed\n");
			// not connected
			reply.rep = SOCKS5_RESPONSE_NETWORK_UNREACHABLE;
			breset(server->buf);
			bconcat(server->buf, (void *)&reply, sizeof(reply));
			server->stage = SOCKS5_CONN_STAGE_CLOSING;
			ev_io_start(EV_A_ &server->send_ctx->io);
			ev_timer_start(EV_A_ &server->send_ctx->watcher);

			ev_io_stop(EV_A_ &remote->send_ctx->io);
			return;
		}
	}

	pr_pkt("remote send: ", remote->buf->array, remote->buf->len);
	if (remote->buf->len == 0) {
		// close and free
		sys_info_addr("remote send failed", server);
		close_and_free_remote(EV_A_ remote);
		close_and_free_server(EV_A_ server);
		return;
	} else {
		// has data to send
		ssize_t s = send(remote->fd, remote->buf->array + remote->buf->idx,
			remote->buf->len, 0);
		if (s == -1) {
			if (errno != EAGAIN && errno != EWOULDBLOCK) {
				// close and free
				sys_info_addr("remote send failed", server);
				close_and_free_remote(EV_A_ remote);
				close_and_free_server(EV_A_ server);
			}
			return;
		} else if (s < remote->buf->len) {
			// partly sent, move memory, wait for the next time to send
			remote->buf->len -= s;
			remote->buf->idx += s;
			return;
		} else {
			// all sent out, wait for reading
			breset(remote->buf);
			ev_io_stop(EV_A_ & remote_send_ctx->io);
			ev_timer_stop(EV_A_ & remote_send_ctx->watcher);
		}
	}

	sys_debug("reenable server recv\n");
	ev_io_start(EV_A_ & server->recv_ctx->io);
}

static void remote_timeout_cb(EV_P_ ev_timer *watcher, int revents)
{
	remote_ctx_t *remote_ctx = container_of(watcher, remote_ctx_t, watcher);
	remote_t *remote = remote_ctx->remote;
	server_t *server = remote->server;
	sys_info_addr("remote recv timeout", server);
	close_and_free_remote(EV_A_ remote);
	close_and_free_server(EV_A_ server);
}

static void remote_send_timeout_cb(EV_P_ ev_timer *watcher, int revents)
{
	sys_info("remote user destination send timeout\n");
	remote_ctx_t *remote_ctx = container_of(watcher, remote_ctx_t, watcher);
	remote_t *remote = remote_ctx->remote;
	server_t *server = remote->server;
	sys_info_addr("remote send timeout", server);
	close_and_free_remote(EV_A_ remote);
	close_and_free_server(EV_A_ server);
}


static remote_t *new_remote(EV_P_ int fd, int timeout)
{
	remote_t *remote;
	remote                      = ss_malloc(sizeof(remote_t));
	remote->recv_ctx            = ss_malloc(sizeof(remote_ctx_t));
	remote->send_ctx            = ss_malloc(sizeof(remote_ctx_t));
	remote->buf                 = ss_malloc(sizeof(buffer_t));
	remote->fd                  = fd;
	remote->recv_ctx->remote    = remote;
	remote->send_ctx->remote    = remote;
	remote->server              = NULL;

	ev_io_init(&remote->recv_ctx->io, remote_recv_cb, fd, EV_READ);
	ev_io_init(&remote->send_ctx->io, remote_send_cb, fd, EV_WRITE);
	ev_timer_init(&remote->send_ctx->watcher, remote_send_timeout_cb,
		ev_time() - ev_now(EV_A) + timeout, 0);
	ev_timer_init(&remote->recv_ctx->watcher, remote_timeout_cb,
		ev_time() - ev_now(EV_A) + timeout, timeout);

	balloc(remote->buf, BUF_SIZE);

	return remote;
}

remote_t *create_remote(EV_P_ struct addrinfo *info, server_t *server)
{
	int remotefd, r;
	remotefd = socket(info->ai_family, 
		info->ai_socktype, info->ai_protocol);
	if(remotefd == -1) {
		sys_err("socket failed: %s(%d)\n",
			strerror(errno), errno);
		return NULL;
	}

	setnonblocking(remotefd);
	remote_t *remote = new_remote(EV_A_ remotefd, args_info.timeout_arg);
	/* wait on remote connected event */
	ev_io_stop(EV_A_ & server->recv_ctx->io);
	remote->server = server;
	server->remote = remote;

	r = connect(remotefd, info->ai_addr, info->ai_addrlen);
	if (r == -1 && errno != EINPROGRESS) {
		sys_err("connect failed\n");
		close_and_free_remote(EV_A_ remote);
		return NULL;
	} else if (r == 0) {
		sys_debug("remote connected immediately\n");
		remote_send_cb(EV_A_ & remote->send_ctx->io, 0);
	} else {
		ev_io_start(EV_A_ & remote->send_ctx->io);
		ev_timer_start(EV_A_ & remote->send_ctx->watcher);
	}

	return remote;
}

static void server_recv_cb(EV_P_ ev_io *w, int revents)
{
	server_ctx_t *server_recv_ctx = container_of(w, server_ctx_t, io);
	server_t *server              = server_recv_ctx->server;
	remote_t *remote              = server->remote;
	buffer_t *buf;
	ssize_t r;

	if (remote == NULL) {
		buf = server->buf;
	} else {
		buf = remote->buf;
	}

	ev_timer_again(EV_A_ &server->recv_ctx->watcher);
	bconcat_pre(buf, MSS);
	r = recv(server->fd, buf_offset(buf), MSS, 0);
	if (r == 0) {
		/* server connection closed */
		sys_info_addr("server connection closed", server);
		close_and_free_remote(EV_A_ remote);
		close_and_free_server(EV_A_ server);
		return;
	} else if (r == -1) {
		if (errno == EAGAIN || errno == EWOULDBLOCK) {
			/* continue to wait for recv */
			return;
		} else {
			sys_err("recv failed: %s(%d)\n",
				strerror(errno), errno);
			sys_info_addr("server connection closed", server);
			/* server connection closed */
			close_and_free_remote(EV_A_ remote);
			close_and_free_server(EV_A_ server);
			return;
		}
	} else {
		bconcat_suf(buf, r);
		pr_pkt("server client pkt:", buf->array, buf->len);
	}

	switch(server->stage) {
	case SOCKS5_CONN_STAGE_EXHOST:
		{
			struct socks5_request *req = (struct socks5_request *)buf->array;
			if (req->ver != SOCKS5_VERSION) {
				sys_err("socks5 version error\n");
				goto close_conn;
			}

			if(buf->len < sizeof(struct socks5_request)) {
				sys_debug("need more data\n");
				return;
			}

			struct socks5_response reply = {
				SOCKS5_VERSION,
				SOCKS5_RESPONSE_SERVER_FAILURE,
				SOCKS5_RSV,
				SOCKS5_ADDRTYPE_IPV4
			};

			if (req->cmd != SOCKS5_CMD_CONNECT) {
				sys_debug("not supported cmd: [%d]\n", req->cmd);
				reply.rep = SOCKS5_RESPONSE_COMMAND_NOT_SUPPORTED;
				goto response_fail;
			}

			struct addrinfo info;
			struct sockaddr_storage storage;
			memset(&storage, 0, sizeof(struct sockaddr_storage));
			static char peer_name[INET6_ADDRSTRLEN] = { 0 };

			sys_debug("addrtype [%d]\n", req->addrtype);
			switch (req->addrtype) {
			case SOCKS5_ADDRTYPE_IPV4: 
				{
					if (buf->len < (sizeof(struct socks5_request) + 6)) {
						sys_debug("wait more data\n");
						return;
					}

					struct sockaddr_in *addr = (struct sockaddr_in *)&storage;
					addr->sin_family = AF_INET;

					char *host = buf->array + sizeof(struct socks5_request);
					char *port = host + 4;
					memcpy(&addr->sin_addr.s_addr, host, 4);
					memcpy(&addr->sin_port, port, 2);

#ifdef CONNLOG
					struct sockaddr_in *tmpaddr = 
						(struct sockaddr_in *)&server->dst.storage;
					server->dst.addrtype =  SOCKS5_ADDRTYPE_IPV4;
					server->dst.storage.ss_family = AF_INET;
					memcpy(&tmpaddr->sin_addr, host, 4);
					memcpy(&tmpaddr->sin_port, port, 2);
#endif

					info.ai_family   = AF_INET;
					info.ai_socktype = SOCK_STREAM;
					info.ai_protocol = IPPROTO_TCP;
					info.ai_addrlen  = sizeof(struct sockaddr_in);
					info.ai_addr     = (struct sockaddr *)addr;
					inet_ntop(AF_INET, &addr->sin_addr, peer_name, INET_ADDRSTRLEN);
					sys_info("connecting: %s %u\n", peer_name, ntohs(*((uint16_t *)port)));
					break;
				}
			case SOCKS5_ADDRTYPE_DOMAIN: 
				{
					if (buf->len < (sizeof(struct socks5_request) + 1)) {
						sys_debug("wait more data\n");
						return;
					}

					int hostname_len = *(buf->array + sizeof(struct socks5_request));
					if (buf->len < (sizeof(struct socks5_request) + hostname_len + 3)) {
						sys_debug("wait more data\n");
						return;
					}

					char *host = buf->array + sizeof(struct socks5_request) + 1;
					char *port = buf->array + sizeof(struct socks5_request) + 1 + hostname_len;
#ifdef CONNLOG
					server->dst.addrtype =  SOCKS5_ADDRTYPE_DOMAIN;
					server->dst.host.hostlen = hostname_len;
					strcpy(server->dst.host.host, host);
					server->dst.host.host[hostname_len] = 0;
					memcpy(&server->dst.host.port, port, 2);
#endif

					uint16_t sin_port;
					memcpy(&sin_port, port, 2);
					host[hostname_len] = 0;

					struct addrinfo hints;
					struct addrinfo *result;
					int s;

					memset(&hints, 0, sizeof(struct addrinfo));
					hints.ai_family   = AF_UNSPEC;
					hints.ai_socktype = SOCK_STREAM;
					s = getaddrinfo(host, NULL, &hints, &result);
					if (s != 0) {
						sys_err("getaddrinfo: %s\n", gai_strerror(s));
						reply.rep = SOCKS5_RESPONSE_NETWORK_UNREACHABLE;
						goto response_fail;
					}
					if(result->ai_family == AF_INET) {
						struct sockaddr_in *addr = (struct sockaddr_in *)&storage;
						addr->sin_family = AF_INET;
						memcpy(addr, result->ai_addr, sizeof(struct sockaddr_in));
						addr->sin_port   = sin_port;

						info.ai_family   = AF_INET;
						info.ai_socktype = SOCK_STREAM;
						info.ai_protocol = IPPROTO_TCP;
						info.ai_addrlen  = sizeof(struct sockaddr_in);
						info.ai_addr     = (struct sockaddr *)addr;
						inet_ntop(AF_INET, &addr->sin_addr, peer_name, INET_ADDRSTRLEN);
						sys_info("connecting: %s %u\n", peer_name, ntohs(sin_port));
					} else if(result->ai_family == AF_INET6) {
						struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&storage;
						addr->sin6_family = AF_INET6;
						memcpy(addr, result->ai_addr, sizeof(struct sockaddr_in6));
						addr->sin6_port   = sin_port;

						info.ai_family    = AF_INET6;
						info.ai_socktype = SOCK_STREAM;
						info.ai_protocol = IPPROTO_TCP;
						info.ai_addrlen   = sizeof(struct sockaddr_in6);
						info.ai_addr      = (struct sockaddr *)addr;
						inet_ntop(AF_INET6, &addr->sin6_addr, peer_name, INET6_ADDRSTRLEN);
						sys_info("connecting: %s %u\n", peer_name, ntohs(sin_port));
					} else {
						sys_err("unknow address type\n");
						reply.rep = SOCKS5_RESPONSE_NETWORK_UNREACHABLE;
						freeaddrinfo(result);
						goto response_fail;
					}
					freeaddrinfo(result);
					break;
				}
			case SOCKS5_ADDRTYPE_IPV6: 
				{
					if (buf->len < (sizeof(struct socks5_request) + 18)) {
						sys_debug("wait more data\n");
						return;
					}

					struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&storage;
					addr->sin6_family = AF_INET6;

					char *host = buf->array + sizeof(struct socks5_request);
					char *port = host + 16;
					memcpy(&addr->sin6_addr, host, 16);
					memcpy(&addr->sin6_port, port, 2);

					info.ai_family   = AF_INET6;
					info.ai_socktype = SOCK_STREAM;
					info.ai_protocol = IPPROTO_TCP;
					info.ai_addrlen  = sizeof(struct sockaddr_in6);
					info.ai_addr     = (struct sockaddr *)addr;
					inet_ntop(AF_INET6, &addr->sin6_addr, peer_name, INET6_ADDRSTRLEN);
					sys_info("connecting: %s %u\n", peer_name, ntohs(*((uint16_t *)port)));
					break;
				}
			default:
				sys_err("not supported addrtype: [%d]\n", req->addrtype);
				reply.rep = SOCKS5_RESPONSE_ADDRTYPE_NOT_SUPPORTED;
				goto response_fail;
			}

			assert(remote == NULL);
			sys_debug("create remote connection\n");
			remote = create_remote(EV_A_ &info, server);
			if (remote == NULL) {
				sys_info_addr("create remote connection failed", server);
				close_and_free_server(EV_A_ server);
				return;
			}
			ev_io_stop(EV_A_ &server_recv_ctx->io);
			break;
response_fail:
			server->stage = SOCKS5_CONN_STAGE_CLOSING;
			breset(buf);
			bconcat(buf, (char *)&reply, sizeof(reply));

			ev_io_stop(EV_A_ &server_recv_ctx->io);
			ev_io_start(EV_A_ &server->send_ctx->io);
			ev_timer_start(EV_A_ &server->send_ctx->watcher);
			break;
		}
	case SOCKS5_CONN_STAGE_STREAM: 
		{
			// send to remote
			ev_io_stop(EV_A_ &server_recv_ctx->io);
			ev_io_start(EV_A_ &remote->send_ctx->io);
			ev_timer_start(EV_A_ &remote->send_ctx->watcher);
			break;
		}
	default:
		sys_err("unexpect stage [%d]\n", server->stage);
		goto close_conn;
	}

	// continue
	return;

close_conn:
	sys_info_addr("create remote connection failed", server);
	close_and_free_remote(EV_A_ remote);
	close_and_free_server(EV_A_ server);
}

static void server_send_cb(EV_P_ ev_io *w, int revents)
{
	server_ctx_t *server_send_ctx = container_of(w, server_ctx_t, io);
	server_t *server              = server_send_ctx->server;
	remote_t *remote              = server->remote;

	/* connect */
	if(!server_send_ctx->connected) {
		struct sockaddr_storage addr;
		socklen_t len = sizeof(addr);
		int r  = getpeername(server->fd, (struct sockaddr *)&addr, &len);
		if (r == 0) {
			server_send_ctx->connected = 1;
			/* send bind info */
		} else {
			sys_err("server connect failed\n");
			close_and_free_remote(EV_A_ remote);
			close_and_free_server(EV_A_ server);
			return;
		}
	}

	pr_pkt("server send: ", server->buf->array, server->buf->len);
	if (server->buf->len == 0) {
		sys_info_addr("server send failed", server);
		close_and_free_remote(EV_A_ remote);
		close_and_free_server(EV_A_ server);
		return;
	} else {
		// has data to send
		ssize_t s = send(server->fd, server->buf->array + server->buf->idx,
			server->buf->len, 0);
		if (s == -1) {
			if (errno != EAGAIN && errno != EWOULDBLOCK) {
				sys_info_addr("server send failed", server);
				close_and_free_remote(EV_A_ remote);
				close_and_free_server(EV_A_ server);
			}
			return;
		} else if (s < (ssize_t)(server->buf->len)) {
			server->buf->len -= s;
			server->buf->idx += s;
			return;
		} else {
			breset(server->buf);
			ev_io_stop(EV_A_ & server_send_ctx->io);
			ev_timer_stop(EV_A_ & server_send_ctx->watcher);
		}
	}

	if(server->stage == SOCKS5_CONN_STAGE_EXHOST) {
		ev_io_start(EV_A_ & server->recv_ctx->io);
		ev_timer_again(EV_A_ & server->recv_ctx->watcher);
	} else if(server->stage == SOCKS5_CONN_STAGE_STREAM) {
		sys_debug("reenable remote recv\n");
		ev_io_start(EV_A_ & remote->recv_ctx->io);
		ev_timer_again(EV_A_ & remote->recv_ctx->watcher);
	} else if(server->stage == SOCKS5_CONN_STAGE_CLOSING) {
		close_and_free_remote(EV_A_ remote);
		close_and_free_server(EV_A_ server);
		return;
	}
}

static void server_timeout_cb(EV_P_ ev_timer *watcher, int revents)
{
	server_ctx_t *server_ctx = container_of(watcher, server_ctx_t, watcher);
	server_t *server = server_ctx->server;
	sys_info_addr("server timeout", server);
	remote_t *remote = server->remote;
	close_and_free_remote(EV_A_ remote);
	close_and_free_server(EV_A_ server);
}

static server_t *new_server(EV_P_ int fd, int timeout)
{
	server_t *server;
	server = ss_malloc(sizeof(server_t));

	server->recv_ctx            = ss_malloc(sizeof(server_ctx_t));
	server->send_ctx            = ss_malloc(sizeof(server_ctx_t));
	server->buf                 = ss_malloc(sizeof(buffer_t));
	server->fd                  = fd;
	server->recv_ctx->server    = server;
	server->send_ctx->server    = server;
	server->stage		    = SOCKS5_CONN_STAGE_EXHOST;

	ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ);
	ev_io_init(&server->send_ctx->io, server_send_cb, fd, EV_WRITE);
	ev_timer_init(&server->send_ctx->watcher, server_timeout_cb,
		ev_time() - ev_now(EV_A) + timeout, 0);
	ev_timer_init(&server->recv_ctx->watcher, server_timeout_cb,
		ev_time() - ev_now(EV_A) + timeout, timeout);

	balloc(server->buf, BUF_SIZE);

	list_add_tail(&server->entries, &connections);

	return server;
}

server_t *create_server(EV_P_ char *host, char *port, void *bind, int len)
{
	int serverfd, r;

	serverfd = create_and_connect_noblock(host, port, &r);
	if(serverfd == -1) {
		sys_err("connect %s, %s failed\n",
			host, port);
		return NULL;
	}

	server_t *server = new_server(EV_A_ serverfd, args_info.timeout_arg);
	bconcat(server->buf, bind, len);

	/* connecting, wait until connected */
	if (r == -1 && errno != EINPROGRESS) {
		sys_err("connect failed\n");
		close_and_free_server(EV_A_ server);
		return NULL;
	} else if (r == 0) {
		sys_debug("connected immediately\n");
		server_send_cb(EV_A_ &server->send_ctx->io, 0);
	} else {
		/* wait on remote connected event */
		ev_io_start(EV_A_ & server->send_ctx->io);
		ev_timer_start(EV_A_ & server->send_ctx->watcher);
	}

	return server;
}

static void conn_recv_cb(EV_P_ ev_io *w, int revents)
{
	conn_ctx_t *conn_recv_ctx   = container_of(w, conn_ctx_t, io);
	conn_t *conn		    = conn_recv_ctx->conn;
	conn_ctx_t *conn_send_ctx   = conn->send_ctx;

	ssize_t r;
	buffer_t *buf;
	buf = conn->buf;

	bconcat_pre(buf, MSS);
	r = recv(conn->fd, buf_offset(buf), MSS, 0);
	sys_debug("fd: %d, size: %ld\n", conn->fd, r);
	if (r == 0) {
		/* server connection closed */
		close_and_free_conn(EV_A_ conn);
		return;
	} else if (r == -1) {
		if (errno == EAGAIN || errno == EWOULDBLOCK) {
			/* continue to wait for recv */
			return;
		} else {
			sys_err("recv failed: %s(%d)\n",
				strerror(errno), errno);
			/* server connection closed */
			close_and_free_conn(EV_A_ conn);
			return;
		}
	} else {
		bconcat_suf(buf, r);
		pr_pkt("router client pkt:", buf->array, buf->len);
	}

	while(buf->len > 0) {
		switch(buf->array[0]) {
		case CMD_PONG:
			{
				struct router_pong *pong = (void *)buf->array;
				if(buf->len < sizeof(struct router_pong)) {
					sys_debug("need more data\n");
					return;
				}

				if(strcmp(pong->data, "PONG") != 0) {
					sys_err("error pong packet\n");
					goto conn_close;
				}
				bshrink(buf, sizeof(struct router_pong));
				break;
			}
		case CMD_BIND:
			{
				struct router_bind *bind = (void *)buf->array;
				if(buf->len < sizeof(struct router_bind)) {
					sys_debug("need more data\n");
					return;
				}

				create_server(EV_A_ args_info.server_arg,
					args_info.remote_port_arg,
					bind, sizeof(struct router_bind));

				bshrink(buf, sizeof(struct router_bind));
				break;
			}
		case CMD_REBOOT:
			{
				sys_err("recv server reboot command\n");
#ifdef CURL_FOUND
				retrytimes = MAXRETRY;
#endif
				goto conn_close;
				break;
			}
		default:
			breset(buf);
			sys_err("error router cmd\n");
			return;
		}
	}
	return;
conn_close:
	close_and_free_conn(EV_A_ conn);
}

static void conn_timeout_cb(EV_P_ ev_timer *watcher, int revents)
{
	conn_ctx_t *conn_ctx = container_of(watcher, conn_ctx_t, watcher);
	conn_t *conn = conn_ctx->conn;
	sys_debug("conn timeout\n");
	close_and_free_conn(EV_A_ conn);
}

static void conn_heartbeat_cb(EV_P_ ev_timer *watcher, int revents)
{
	conn_t *conn = container_of(watcher, conn_t, heartbeat);
	sys_debug("heartbeat\n");
	ev_timer_again(EV_A_ &conn->heartbeat);
	if(bufempty(conn->buf))
		ev_io_stop(EV_A_ &conn->recv_ctx->io);
	else {
		sys_debug("have data now, skip heartbeat\n");
		return;
	}

	struct router_ping ping = {
		CMD_PING,
		"PING"
	};

	breset(conn->buf);
	bconcat(conn->buf, (void *)&ping, sizeof(ping));

	ev_io_start(EV_A_ &conn->send_ctx->io);
	ev_timer_start(EV_A_ &conn->send_ctx->watcher);
}

conn_t *new_conn(EV_P_ int fd, int timeout)
{
	conn_t *conn;
	conn = ss_malloc(sizeof(conn_t));

	conn->fd		= fd;
	conn->buf		= ss_malloc(sizeof(buffer_t));
	conn->recv_ctx		= ss_malloc(sizeof(conn_ctx_t));
	conn->send_ctx		= ss_malloc(sizeof(conn_ctx_t));
	conn->recv_ctx->conn	= conn;
	conn->send_ctx->conn	= conn;

	ev_io_init(&conn->recv_ctx->io, conn_recv_cb, fd, EV_READ);
	ev_io_init(&conn->send_ctx->io, conn_send_cb, fd, EV_WRITE);
	ev_timer_init(&conn->send_ctx->watcher, conn_timeout_cb,
		ev_time() - ev_now(EV_A) + timeout, 0);

	if(args_info.heartbeat_arg > 0) {
		ev_timer_init(&conn->heartbeat, conn_heartbeat_cb,
			ev_time() - ev_now(EV_A) + args_info.heartbeat_arg, args_info.heartbeat_arg);
	}

	balloc(conn->buf, BUF_SIZE);
	return conn;
}

conn_t *create_conn(EV_P_ char *server, char *port)
{
	int conn_sock;

	conn_sock = create_and_connect(server, port);
	if(conn_sock == -1) {
		sys_err("connect %s, %s failed\n",
			server, port);
		return NULL;
	}
	setnonblocking(conn_sock);
	tcp_alive(conn_sock);

	conn_t *conn = new_conn(EV_A_ conn_sock, args_info.timeout_arg);
	return conn;
}

int nicmac(char *nic, uint8_t *mac)
{
	int ret;
	struct ifreq req;
	strcpy(req.ifr_name, nic);
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd < 0) {
		sys_err("Create socket failed\n");
		exit(-1);
	}
	ret = ioctl(sockfd, SIOCGIFHWADDR, &req);
	if(ret < 0) {
		sys_err("Get ap mac failed\n");
		exit(-1);
	}
	memcpy(mac, req.ifr_hwaddr.sa_data, 6);
	close(sockfd);
	return 0;
}

static int one_instance()
{
	int pid_file = open("/var/run/dyncli.pid", 
		O_CREAT | O_RDWR, 0666);
	int rc = flock(pid_file, LOCK_EX | LOCK_NB);
	if(rc) {
		if(EWOULDBLOCK == errno) {
			sys_warn("another dyncli is running\n");
			return -1;
		}
	}
	return 0;
}

static int one_instance_super()
{
	int pid_file = open("/var/run/dynclisuper.pid", 
		O_CREAT | O_RDWR, 0666);
	int rc = flock(pid_file, LOCK_EX | LOCK_NB);
	if(rc) {
		if(EWOULDBLOCK == errno) {
			sys_warn("another dyncli is running\n");
			return -1;
		}
	}
	return 0;
}



#ifdef CURL_FOUND
#define NAMESERVER_SERVER 	"\"server\":\""
#define NAMESERVER_PORT 	"\"routerport\":\""
#define NAMESERVER_REMOTEPORT 	"\"remoteport\":\""

static char nameserver[1024];
size_t nameserver_parse( char *ptr, size_t size, size_t  nmemb,  void *userdata)
{
	sys_debug("ptr: %s\n", ptr);
	strncat(nameserver, ptr, size * nmemb);

	return size * nmemb;
}

void keyfree(char *ptr)
{
	if(ptr)
		free(ptr);
}

static char *getkey(char *buf, char *key)
{
	char *tmp, *value;
	tmp = strstr(buf, key);
	if(!tmp)
		return NULL;
	tmp += strlen(key);

	int len = 0;
	while(tmp[len] != '"' && tmp[len] != 0)
		len++;
	value = malloc(len + 1);
	if(!value)
		return NULL;
	strncpy(value, tmp, len);
	value[len] = 0;
	return value;
}

static void nameserver_query()
{
	int ret;
	char url[1024];
	snprintf(url, 1024, "%s/nameserver/api/router",
		args_info.nameserver_arg);

retry:
	nameserver[0] = 0;
	ret = http_send(url, NULL, nameserver_parse, NULL);
	if(ret < 0) {
		sys_err("nameserver router %s failed, retrying...\n",
			url);
		sleep(10);
		goto retry;
	}

	sys_debug("nameserver result: %s\n", nameserver);
	keyfree(args_info.server_arg);
	keyfree(args_info.port_arg);
	keyfree(args_info.remote_port_arg);
	args_info.server_arg = getkey(nameserver, NAMESERVER_SERVER);
	args_info.port_arg = getkey(nameserver, NAMESERVER_PORT);
	args_info.remote_port_arg = getkey(nameserver, NAMESERVER_REMOTEPORT);
	if(args_info.server_arg == NULL ||
		args_info.port_arg == NULL ||
		args_info.remote_port_arg == NULL) {
		sys_err("nameserver error: %s\n", nameserver);
		sleep(10);
		goto retry;
	}
	sys_debug("nameserver : %s %s %s\n",
		args_info.server_arg,
		args_info.port_arg,
		args_info.remote_port_arg);
}
#endif

int conninit()
{
	if(daemon_mode)
		daemon(0, 0);
	/* linux have no SO_NOSIGPIPE */
	signal(SIGPIPE, SIG_IGN);
	struct ev_loop *loop = EV_DEFAULT;

	conn_t *conn;

#ifdef CURL_FOUND
retrynameserver:
	retrytimes = 0;
	if(args_info.nameserver_given &&
		strlen(args_info.nameserver_arg) != 0)
		nameserver_query();
#endif

reconnect:
	conn = create_conn(loop, args_info.server_arg, args_info.port_arg);
	if(!conn) {
		sleep(1);
#ifdef CURL_FOUND
		if(retrytimes++ >= MAXRETRY)
			goto retrynameserver;
#endif
		goto reconnect;
	}

	/* first register */
	struct router_reg reg;
	reg.cmd = CMD_REG;
	reg.routerinfo.routerlevel = args_info.routerlevel_arg;
	reg.routerinfo.wantype = args_info.wantype_arg;
	reg.routerinfo.speed = args_info.speed_arg;
	if(args_info.nicmac_given) {
		int nread;
		char a[2];
		uint8_t *mac = (uint8_t *)&(reg.routerinfo.mac);
		nread = sscanf(args_info.nicmac_arg, 
			"%hhx%[-:]%hhx%[-:]%hhx%[-:]%hhx%[-:]%hhx%[-:]%hhx", 
			mac + 0,&a[0],
			mac + 1,&a[0],
			mac + 2,&a[0],
			mac + 3,&a[0],
			mac + 4,&a[0],
			mac + 5); 
		if(nread < 10) {
			sys_err("%s process failed\n", args_info.nicmac_arg);
			exit(-1);
		}
	} else {
		nicmac(args_info.nic_arg, reg.routerinfo.mac);
	}

	breset(conn->buf);
	bconcat(conn->buf, (void *)&reg, sizeof(reg));
	sys_debug("\nregister new router: \n"
		"\tlevel: %d\n"
		"\twantype: %d\n"
		"\tspeed: %d\n"
		"\tmac: %02x:%02x:%02x:%02x:%02x:%02x\n",
		reg.routerinfo.routerlevel,
		reg.routerinfo.wantype,
		reg.routerinfo.speed,
		reg.routerinfo.mac[0],
		reg.routerinfo.mac[1],
		reg.routerinfo.mac[2],
		reg.routerinfo.mac[3],
		reg.routerinfo.mac[4],
		reg.routerinfo.mac[5]
		);

	ev_io_start(EV_A_ &conn->send_ctx->io);
	ev_timer_start(EV_A_ &conn->send_ctx->watcher);
	if(args_info.heartbeat_arg > 0)
		ev_timer_start(EV_A_ &conn->heartbeat);

	/* enter loop */
	ev_run(loop, 0);

	/* no watcher reconnect */
	sys_info("server lost reconnecting\n");
	goto reconnect;
	return 0;
}

int main(int argc, char *argv[])
{
	proc_args(argc, argv);
	pid_t pid = fork();
	if(pid == 0) {
		/* child */
		if(one_instance_super() < 0)
			return 0;
		if(!args_info.super_debug_flag)
			debug = 0;
		args_info.nameserver_arg = args_info.super_nameserver_arg;
		args_info.server_arg = args_info.super_server_arg;
		args_info.port_arg = args_info.super_port_arg;
		args_info.remote_port_arg = args_info.super_remote_port_arg;
		conninit();
		exit(0);
	} else if(pid > 0) {
		/* parent */
		if(one_instance() < 0)
			return 0;
		conninit();
		exit(0);
	} else {
		exit(-1);
	}
	return 0;
}
